{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "gmic_video",
      "private_outputs": true,
      "provenance": [],
      "collapsed_sections": [],
      "machine_shape": "hm",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    },
    "accelerator": "GPU"
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/eyaler/avatars4all/blob/master/gmic_video.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "D_nPMNtfIGYD"
      },
      "source": [
        "# A simple web interface for running [G'MIC](https://gmic.eu) on videos ([j.mp/gmicvid](https://j.mp/gmicvid))\n",
        "\n",
        "### Made just a little bit more accessible by Eyal Gruss ([@eyaler](twitter.com/eyaler) / [eyalgruss.com](https://eyalgruss.com) / eyalgruss@gmail.com)\n",
        "\n",
        "##### A curated list of online generative tools: [j.mp/generativetools](https://j.mp/generativetools)"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "CFoUb-eoWv5y",
        "cellView": "form"
      },
      "source": [
        "#@title Setup\n",
        "!pip install gmic\n",
        "!pip install imageio-ffmpeg\n",
        "!pip install youtube-dl"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "cellView": "form",
        "id": "HjNe-pkCjY21"
      },
      "source": [
        "#@title Optionally mount Google Drive { run: \"auto\" }\n",
        "mount_google_drive = False #@param {type:\"boolean\"}\n",
        "if mount_google_drive:\n",
        "  from google.colab import drive\n",
        "  drive.mount('/content/drive')"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "eQIQ5fv_u-Vq",
        "cellView": "form"
      },
      "source": [
        "#@title Get video\n",
        "#@markdown 1. You can change the URL to your **own** stuff! (e.g. youtube, mp4)\n",
        "#@markdown 2. Advanced: You can add files mannualy (e.g. from a mounted Google drive) and put in the path the location on Colab.\n",
        "#@markdown 3. Alternatively, you select Upload to **manually upload** files from your machine. After pressing play, a button will appear bellow to select files.\n",
        "\n",
        "import os\n",
        "from google.colab import files\n",
        "\n",
        "\n",
        "url = 'https://www.youtube.com/watch?v=kMpnwIGDQvU' #@param ['https://www.youtube.com/watch?v=kMpnwIGDQvU', 'https://www.youtube.com/watch?v=IEqccPhsqgA', 'Upload'] {allow-input: true}\n",
        "\n",
        "if url:\n",
        "  %cd /content\n",
        "  if os.path.isfile(url):\n",
        "    full_video = os.path.abspath(url)\n",
        "  elif url == 'Upload':\n",
        "    %cd /content/sample_data\n",
        "    try:\n",
        "      uploaded = files.upload()\n",
        "    except Exception:\n",
        "      %cd /content\n",
        "      raise\n",
        "    for fn in uploaded:\n",
        "      full_video = os.path.abspath(fn)\n",
        "      break\n",
        "    %cd /content\n",
        "  else:\n",
        "    full_video = '/content/full.mp4'\n",
        "    !rm -f $full_video\n",
        "    !youtube-dl --no-check-extensions --no-playlist -f \"bestvideo[ext=mp4][vcodec!*=av01]+bestaudio[ext=m4a]/mp4\" \"$url\" --merge-output-format mp4 -o $full_video\n",
        "    if not os.path.exists(full_video):\n",
        "      full_video = '/content/full.'+url.rsplit('.',1)[1]\n",
        "      !wget \"$url\" -O $full_video\n",
        "  input_video = '/content/input.'+full_video.rsplit('.',1)[1]\n",
        "  !cp \"$full_video\" $input_video"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "mbAexuckWTHJ",
        "cellView": "form"
      },
      "source": [
        "#@title Optionally shorten video\n",
        "start_seconds =  0#@param {type:\"number\"}\n",
        "duration_seconds =  10#@param {type:\"number\"}\n",
        "start_seconds = max(start_seconds,0)\n",
        "duration_seconds = max(duration_seconds,0)\n",
        "\n",
        "if duration_seconds:\n",
        "  !rm $input_video\n",
        "  !ffmpeg -ss $start_seconds -t $duration_seconds -i \"$full_video\" $input_video -y\n",
        "else:\n",
        "  !cp \"$full_video\" $input_video"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "6aNx0BDLnhZx",
        "cellView": "form"
      },
      "source": [
        "#@title G'MIC it!\n",
        "#@markdown You can freely edit the filters and parameters! (you can copy them from https://gmic.eu/gallery or G'MIC-Qt https://www.fosshub.com/GMIC.html?dwl=gmic_2.9.7_qt_win64.zip).\n",
        "\n",
        "#@markdown Note: Frame mode is slower but it gives better quality results and can deal with larger videos.\n",
        "frame_mode = True #@param {type:\"boolean\"}\n",
        "filter_1 = 'fx_polygonize 300,10,10,10,10,0,0,0,255,0' #@param [' ', 'fx_boxfitting 3,0,0.1,0', 'cartoon 3,200,20,0.25,1.5,8,0', 'fx_diffusiontensors 10,5,3,1,0.15,1,0,3,0', 'fx_engrave_preview 0.5,50,0,8,40,0,0,0,10,1,0,0,0,1,0', 'fx_engrave_preview 0.5,50,0,8,40,0,0,1,10,1,0,0,0,1,0', 'fx_pixelsort 1,0,0,1,0,100,0,0,0', 'fx_polygonize 300,10,10,10,10,0,0,0,255,0', 'rodilius 10,10,300,5,30,1,1,0,0,0', 'fx_stained_glass 20,0.1,1,1,1,0,0,0,0', 'fx_8bits 25,800,16,0', 'fx_normalize_local 2,6,5,20,1,11,0'] {allow-input: true}\n",
        "filter_1_enabled = True #@param {type:\"boolean\"}\n",
        "filter_2 = ' ' #@param [' ', 'fx_boxfitting 3,0,0.1,0', 'cartoon 3,200,20,0.25,1.5,8,0', 'fx_diffusiontensors 10,5,3,1,0.15,1,0,3,0', 'fx_engrave_preview 0.5,50,0,8,40,0,0,0,10,1,0,0,0,1,0', 'fx_engrave_preview 0.5,50,0,8,40,0,0,1,10,1,0,0,0,1,0', 'fx_pixelsort 1,0,0,1,0,100,0,0,0', 'fx_polygonize 300,10,10,10,10,0,0,0,255,0', 'rodilius 10,10,300,5,30,1,1,0,0,0', 'fx_stained_glass 20,0.1,1,1,1,0,0,0,0', 'fx_8bits 25,800,16,0', 'fx_normalize_local 2,6,5,20,1,11,0'] {allow-input: true}\n",
        "filter_2_enabled = True #@param {type:\"boolean\"}\n",
        "filter_3 = ' ' #@param [' ', 'fx_boxfitting 3,0,0.1,0', 'cartoon 3,200,20,0.25,1.5,8,0', 'fx_diffusiontensors 10,5,3,1,0.15,1,0,3,0', 'fx_engrave_preview 0.5,50,0,8,40,0,0,0,10,1,0,0,0,1,0', 'fx_engrave_preview 0.5,50,0,8,40,0,0,1,10,1,0,0,0,1,0', 'fx_pixelsort 1,0,0,1,0,100,0,0,0', 'fx_polygonize 300,10,10,10,10,0,0,0,255,0', 'rodilius 10,10,300,5,30,1,1,0,0,0', 'fx_stained_glass 20,0.1,1,1,1,0,0,0,0', 'fx_8bits 25,800,16,0', 'fx_normalize_local 2,6,5,20,1,11,0'] {allow-input: true}\n",
        "filter_3_enabled = True #@param {type:\"boolean\"}\n",
        "filter_4 = ' ' #@param [' ', 'fx_boxfitting 3,0,0.1,0', 'cartoon 3,200,20,0.25,1.5,8,0', 'fx_diffusiontensors 10,5,3,1,0.15,1,0,3,0', 'fx_engrave_preview 0.5,50,0,8,40,0,0,1,10,1,0,0,0,1,0', 'fx_engrave_preview 0.5,50,0,8,40,0,0,0,10,1,0,0,0,1,0', 'fx_pixelsort 1,0,0,1,0,100,0,0,0', 'fx_polygonize 300,10,10,10,10,0,0,0,255,0', 'rodilius 10,10,300,5,30,1,1,0,0,0', 'fx_stained_glass 20,0.1,1,1,1,0,0,0,0', 'fx_8bits 25,800,16,0', 'fx_normalize_local 2,6,5,20,1,11,0'] {allow-input: true}\n",
        "filter_4_enabled = True #@param {type:\"boolean\"}\n",
        "filter_5 = ' ' #@param [' ', 'fx_boxfitting 3,0,0.1,0', 'cartoon 3,200,20,0.25,1.5,8,0', 'fx_diffusiontensors 10,5,3,1,0.15,1,0,3,0', 'fx_engrave_preview 0.5,50,0,8,40,0,0,0,10,1,0,0,0,1,0', 'fx_engrave_preview 0.5,50,0,8,40,0,0,1,10,1,0,0,0,1,0', 'fx_pixelsort 1,0,0,1,0,100,0,0,0', 'fx_polygonize 300,10,10,10,10,0,0,0,255,0', 'rodilius 10,10,300,5,30,1,1,0,0,0', 'fx_stained_glass 20,0.1,1,1,1,0,0,0,0', 'fx_8bits 25,800,16,0', 'fx_normalize_local 2,6,5,20,1,11,0'] {allow-input: true}\n",
        "filter_5_enabled = True #@param {type:\"boolean\"}\n",
        "copy_audio = True #@param {type:\"boolean\"}\n",
        "\n",
        "filters = ' '.join(f for f in [filter_1 if filter_1_enabled else '',filter_2 if filter_2_enabled else '',filter_3 if filter_3_enabled else '',filter_4 if filter_4_enabled else '',filter_5 if filter_5_enabled else ''] if f.strip())\n",
        "\n",
        "import gmic\n",
        "from time import time\n",
        "import imageio\n",
        "from IPython.display import HTML, clear_output\n",
        "import os\n",
        "\n",
        "start = time()\n",
        "!rm -f /content/output.mp4\n",
        "!rm -f /content/gmic.mp4\n",
        "!rm -rf /content/in_frames\n",
        "!rm -rf /content/out_frames\n",
        "with imageio.get_reader(input_video) as reader:\n",
        "  fps = reader.get_meta_data()['fps']\n",
        "g = gmic.Gmic()\n",
        "if frame_mode:\n",
        "  !mkdir -p /content/in_frames\n",
        "  !mkdir -p /content/out_frames\n",
        "  !ffmpeg -i $input_video -compression_level 1 /content/in_frames/frame_%06d.png\n",
        "  files = sorted(os.listdir('/content/in_frames'))\n",
        "  for i,file in enumerate(files, start=1):\n",
        "    if file.endswith('.png'):\n",
        "      cmd = ' '.join(['input',os.path.join('/content/in_frames',file),filters,'output',os.path.join('/content/out_frames',file)])\n",
        "      if i%100==0:\n",
        "        print ('%d/%d'%(i,len(files)))\n",
        "      g.run(cmd)\n",
        "  if copy_audio:\n",
        "    !ffmpeg -framerate $fps -i /content/out_frames/frame_%06d.png -i $input_video -c:v libx264 -c:a aac -map 0:v -map 1:a? -vf \"crop=trunc(iw/2)*2:trunc(ih/2)*2\" -pix_fmt yuv420p -profile:v baseline -movflags +faststart /content/output.mp4 -y\n",
        "  else:\n",
        "    !ffmpeg -framerate $fps -i /content/out_frames/frame_%06d.png -c:v libx264 -vf \"crop=trunc(iw/2)*2:trunc(ih/2)*2\" -pix_fmt yuv420p -profile:v baseline -movflags +faststart /content/output.mp4 -y\n",
        "else:\n",
        "  cmd = ' '.join(['input',input_video,filters,'output','/content/gmic.mp4,%f'%fps])\n",
        "  g.run(cmd)\n",
        "  if copy_audio:\n",
        "    !ffmpeg -i /content/gmic.mp4 -i $input_video -c:v libx264 -c:a aac -map 0:v -map 1:a? -vf \"crop=trunc(iw/2)*2:trunc(ih/2)*2\" -pix_fmt yuv420p /content/output.mp4 -profile:v baseline -movflags +faststart -y\n",
        "  else:\n",
        "    !ffmpeg -i /content/gmic.mp4 -c:v libx264 -vf \"crop=trunc(iw/2)*2:trunc(ih/2)*2\" -pix_fmt yuv420p /content/output.mp4 -profile:v baseline -movflags +faststart -y\n",
        "\n",
        "clear_output()\n",
        "print('cmd:',cmd)\n",
        "print('%stook: %.1f minutes'%('%d frames '%(len(files)) if frame_mode else '', (time()-start)/60))\n",
        "# robust display for large videos (https://stackoverflow.com/questions/67591072/displaying-large-video-files-in-google-colab): \n",
        "!pkill -f \"python3 -m http.server\"\n",
        "!nohup python3 -m http.server -d /content 8000 &>/dev/null &\n",
        "HTML(\"\"\"\n",
        "<video width=600 controls autoplay loop>\n",
        "  <source src=\"https://localhost:8000/output.mp4\" type=\"video/mp4\">\n",
        "</video>\"\"\")\n"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "FOOK5IgRZaLr",
        "cellView": "form"
      },
      "source": [
        "#@title Download\n",
        "from google.colab import files\n",
        "files.download('/content/output.mp4')"
      ],
      "execution_count": null,
      "outputs": []
    }
  ]
}
